package net.m2technologies.open_arm.transaction;

import net.m2technologies.open_arm.transport.transaction.TransactionMediator;
import net.m2technologies.open_arm.utilities.OpenArmUtilities;
import net.m2technologies.open_arm.utilities.collections.CollectionUtils;
import net.m2technologies.open_arm.utilities.collections.ExceptionalClosure;
import net.m2technologies.open_arm.utilities.collections.ExceptionalTransformer;
import org.opengroup.arm40.transaction.*;

import java.util.Collection;
import java.util.Iterator;

/**
 * Copyright 2005 Mark Masterson<br> <br> Licensed under the Apache License, Version 2.0 (the "License");<br> you may
 * not use this file except in compliance with the License.<br> You may obtain a copy of the License at<br> <br>
 * http://www.apache.org/licenses/LICENSE-2.0<br> <br> Unless required by applicable law or agreed to in writing,
 * software<br> distributed under the License is distributed on an "AS IS" BASIS,<br> WITHOUT WARRANTIES OR CONDITIONS
 * OF ANY KIND, either express or implied.<br> See the License for the specific language governing permissions and<br>
 * limitations under the License.<br>
 * <p/>
 *
 * @author Mark Masterson
 * @version 0.010
 */
public class ArmTransactionImpl extends UniqueObjectImpl implements ArmTransaction, TransactionState {

    private final ArmTransactionDefinition transactionDefiniton;
    private final ArmApplication application;
    private ArmUser user;
    private ArmCorrelator parentCorrelator;
    private boolean traceRequested;
    private long startTime;
    private long arrivalTime;
    private long blockedHandle;
    private final ArmIdentityPropertiesTransaction contextProperties;
    private boolean isRunning;
    private ArmCorrelator localCorrelator;
    private int lastStatusSet;
    private final Object eventLock = new Object();

    public ArmTransactionImpl(final ArmTransactionFactory creatingFactory,
                              final ArmTransactionDefinition transactionDefiniton,
                              final ArmApplication app,
                              final String[] contextValues) {
        super(creatingFactory.newArmID(null), creatingFactory);
        this.transactionDefiniton = transactionDefiniton;
        this.application = app;
        final ArmIdentityPropertiesImpl existingProps = (ArmIdentityPropertiesImpl) transactionDefiniton.getIdentityProperties();
        if (null != existingProps) {
            this.contextProperties = new ArmIdentityPropertiesTransactionImpl(existingProps);
            ((ArmIdentityPropertiesTransactionImpl) this.contextProperties).setContextValues(contextValues);
        } else {
            this.contextProperties = null;
        }
    }

    private Collection getTransportMediators() {
        return ((OpenArmUtilities) getCreatingFactory()).getTransportMediatorBroker().getTransactionTransportMediators(
                this);
    }

    private void releaseTransportMediators(final Collection mediators) {
        ((OpenArmUtilities) getCreatingFactory()).getTransportMediatorBroker().releaseTransportMediators(mediators);
    }

    /**
     * This method is currently disabled in our implementation, becuase we haven't understood it yet, or found a way to
     * translate it into a sensible Java implementation.  Calling it will provoke a RuntimeException.
     *
     * @return RuntimeException
     *
     * @deprecated because C programmers don't like OO
     */
    public int bindThread() {
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                protected void basicExecute(final Object o) throws Exception {
                    ((TransactionMediator) o).bindThread();
                }
            });
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * Use this method to notify OpenArm that your transaction has reached a point where it will have to wait,
     * synchronously, for some external resource.  Old timers refer to this as "blocking" rather than write out
     * "waitingForExternalResource", which takes too many bytes... :-)
     *
     * @return a "block handle"  -- more old timer stuff.  It's just a number, and it gets incremented each time
     *         somebody calls this method.  You use it as the parameter to {@link #unblocked}, and if we were C
     *         programmers, it might have some meaning.  But we're not, so it doesn't.
     */
    public long blocked() {
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            final Collection results;
            synchronized (this.eventLock) {
                results = CollectionUtils.collect(mediators, new ExceptionalTransformer() {
                    protected Object basicTransform(final Object o) throws Exception {
                        return new Long(((TransactionMediator) o).blocked());
                    }
                });
            }
            if (null != results && !results.isEmpty()) {
                return ((Long) results.iterator().next()).longValue();
            }
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * Each transaction gets initialized with an instance of the ArmApplication within whose context the transaction is
     * running.
     *
     * @return an instance of ArmApplication.
     */
    public ArmApplication getApplication() {
        return this.application;
    }

    public String getContextURIValue() {
        return this.contextProperties.getURIValue();
    }

    public String getContextValue(final int index) {
        return ((ArmIdentityPropertiesImpl) this.contextProperties).getContextValue(index);
    }

    /**
     * You should only call this method within a transactional scope -- that means you should always call {@link #start}
     * first, before you call this method.  If you forget, you'll get an Exception back.<br> Assuming you've met this
     * precondition, however, this method will then apply the following algorithm:<br> -- is there a parent correlator?
     * If yes, return it.<br> -- if not, is there already a local correlator?  If yes, return it.<br> -- if not,
     * instantiate a new correlator, and assign it to the local correlator instance variable.  Subsequent calls to this
     * method within the same transactional scope will return the instance of the local correlator.
     *
     * @return an ArmCorrelator instance or an UnsupportedOperationException if not called within a transactional
     *         scope.
     */
    public ArmCorrelator getCorrelator() {
        if (this.isRunning) {
            if (null != getParentCorrelator()) {
                return getParentCorrelator();
            } else {
                if (null == this.localCorrelator) {
                    this.localCorrelator
                            = new ArmCorrelatorImpl(((OpenArmUtilities) getCreatingFactory()).getGuid(),
                                                    getCreatingFactory());
                }
                return this.localCorrelator;
            }
        } else {
            throw new UnsupportedOperationException(new StringBuffer()
                    .append("This method should not be called without demarcating a running")
                    .append(" transaction via a call to the #start method first!")
                    .toString());
        }
    }

    /**
     * If there was a parent correlator at instantiation, you get it back with this method.
     *
     * @return an ArmCorrelator or null.
     */
    public ArmCorrelator getParentCorrelator() {
        return this.parentCorrelator;
    }

    /**
     * The {@link #stop} method requires an int parameter that represents the completion status of the transaction.  The
     * {@link ArmConstants} interface contains a handful of values that one can use for this.  Each call to {@link
     * #stop} sets an instance variable, which is used to retain the value of this completion status until the next time
     * {@link #stop} is called.  In between {@link #stop} calls, you can retrieve the previous status at any time by
     * calling <b>this</b> method.
     *
     * @return the most recent "completion status" value passed to {@link #stop}
     */
    public int getStatus() {
        return this.lastStatusSet;
    }

    /**
     * Each transaction gets initialized with an instance of the ArmTransactionDefinition that represents the type of
     * the transaction. More weird C stuff.
     *
     * @return an instance of ArmTransactionDefinition.
     */
    public ArmTransactionDefinition getDefinition() {
        return this.transactionDefiniton;
    }

    public ArmUser getUser() {
        return this.user;
    }

    public boolean isTraceRequested() {
        return this.traceRequested;
    }

    /**
     * Calling this will reset all of the counters being maintained within a transactional scope.<br> These include:<br>
     * -- start time<br> -- arrival time<br> -- trace state<br> -- the parent correlator<br>
     *
     * @return Zero. Literally. No kidding!
     */
    public int reset() {
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                protected void basicExecute(final Object o) throws Exception {
                    ((TransactionMediator) o).reset();
                }
            });
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * Used to set the arrival time of the transactional scope.<br> Let's say you've got a transaction with a somewhat
     * complex phase structure, and you have some sort of setup that happens immediately after the transaction starts,
     * but before the point where you think the real action gets going.  You can express this sort of a situation using
     * the arrival time and start time attributes of the transaction together.  You use the arrival time to express the
     * start of your setup phase.  Once the action is about to get going, you subsequently make a call to {@link
     * #start}.  When you later call {@link #stop}, the difference between the arrival time and start time will be
     * differentiated.
     *
     * @return Zero. Literally. No kidding!
     */
    public int setArrivalTime() {
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                protected void basicExecute(final Object o) throws Exception {
                    ((TransactionMediator) o).setArrivalTime();
                }
            });
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    public int setContextURIValue(final String value) {
        ((ArmIdentityPropertiesTransactionImpl) this.contextProperties).setUriValue(value);
        return 0;
    }

    public int setContextValue(final int index, final String value) {
        ((ArmIdentityPropertiesImpl) this.contextProperties).setContextValue(index, value);
        return 0;
    }

    public int setTraceRequested(final boolean traceState) {
        this.traceRequested = traceState;
        return 0;
    }

    public int setUser(final ArmUser user) {
        this.user = user;
        return 0;
    }

    /**
     * Used to start the clock for this transactional scope.
     *
     * @return Zero. Literally. No kidding!
     */
    public int start() {
        this.isRunning = true;
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                protected void basicExecute(final Object o) throws Exception {
                    ((TransactionMediator) o).start();
                }
            });
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * Used to start the clock for this transactional scope.
     *
     * @param parentCorr
     *
     * @return Zero. Literally. No kidding!
     */
    public int start(final byte[] parentCorr) {
        this.isRunning = true;
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                protected void basicExecute(final Object o) throws Exception {
                    ((TransactionMediator) o).start(parentCorr);
                }
            });
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * This method is currently disabled in our implementation, becuase we haven't understood it yet, or found a way to
     * translate it into a sensible Java implementation.  Calling it will provoke a RuntimeException.
     *
     * @return RuntimeException
     *
     * @deprecated because C programmers don't like OO
     */
    public int start(final byte[] parentCorr, final int offset) {
        this.isRunning = true;
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                protected void basicExecute(final Object o) throws Exception {
                    ((TransactionMediator) o).start(parentCorr, offset);
                }
            });
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * Used to start the clock for this transactional scope.
     *
     * @param parentCorr
     *
     * @return Zero. Literally. No kidding!
     */
    public int start(final ArmCorrelator parentCorr) {
        this.isRunning = true;
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            synchronized (this.eventLock) {
                CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                    protected void basicExecute(final Object o) throws Exception {
                        ((TransactionMediator) o).start(parentCorr);
                    }
                });
            }
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * Used to stop the clock for this transactional scope. This method requires an int parameter that represents the
     * completion status of the transaction.  The {@link ArmConstants} interface contains a handful of values that one
     * can use for this.  Each call to {@link #stop} sets an instance variable, which is used to retain the value of
     * this completion status until the next time {@link #stop} is called.  In between {@link #stop} calls, you can
     * retrieve the previous status at any time by calling the {@link #getStatus} method.
     *
     * @param status Completion status of the transaction.
     *
     * @return Zero. Literally. No kidding!
     */
    public int stop(final int status) {
        return stop(status, null);
    }

    /**
     * Used to stop the clock for this transactional scope. This method requires an int parameter that represents the
     * completion status of the transaction.  The {@link ArmConstants} interface contains a handful of values that one
     * can use for this.  Each call to {@link #stop} sets an instance variable, which is used to retain the value of
     * this completion status until the next time {@link #stop} is called.  In between {@link #stop} calls, you can
     * retrieve the previous status at any time by calling the {@link #getStatus} method.
     *
     * @param status           Completion status of the transaction.
     * @param diagnosticDetail Additional message text that should be recorded with the stop.
     *
     * @return Zero. Literally. No kidding!
     */
    public int stop(final int status, final String diagnosticDetail) {
        this.isRunning = false;
        this.lastStatusSet = status;
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            synchronized (this.eventLock) {
//                CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
//                    protected void basicExecute(final Object o) throws Exception {
//                        ((TransactionMediator) o).stop(status, diagnosticDetail);
//                    }
//                });
                final Iterator it = mediators.iterator();
                while (it.hasNext()) {
                    final TransactionMediator mediator = (TransactionMediator) it.next();
                    mediator.stop(status, diagnosticDetail);
                }
            }
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * This method is currently disabled in our implementation, becuase we haven't understood it yet, or found a way to
     * translate it into a sensible Java implementation.  Calling it will provoke a RuntimeException.
     *
     * @return RuntimeException
     *
     * @deprecated because C programmers don't like OO
     */
    public int unbindThread() {
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                protected void basicExecute(final Object o) throws Exception {
                    ((TransactionMediator) o).unbindThread();
                }
            });
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * Use this method to notify OpenArm that your transaction has stopped waiting synchronously, for some external
     * resource.  Old timers refer to this as "blocking" rather than write out "stoppedWaitingForExternalResource",
     * which takes too many bytes... :-)
     *
     * @param blockHandle The magic number that you got as a return value from calling {@link #blocked}
     *
     * @return Zero. Literally. No kidding!
     */
    public int unblocked(final long blockHandle) {
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            synchronized (this.eventLock) {
                CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                    protected void basicExecute(final Object o) throws Exception {
                        ((TransactionMediator) o).unblocked(blockHandle);
                    }
                });
            }
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    /**
     * Use this method to notify OpenArm that your transaction is still out there and alive.  Useful for long running
     * transactions, although it would be more useful if you could pass it a String as a parameter.  Then you could use
     * the method to keep a protocol of the major steps/events within your transaction.  Oh well.  Being able to send a
     * heartbeat of any kind is better than nothing...
     *
     * @return Zero. Literally. No kidding!
     */
    public int update() {
        Collection mediators = null;
        try {
            mediators = getTransportMediators();
            synchronized (this.eventLock) {
                CollectionUtils.forAllDo(mediators, new ExceptionalClosure() {
                    protected void basicExecute(final Object o) throws Exception {
                        ((TransactionMediator) o).update();
                    }
                });
            }
        } finally {
            if (null != mediators) {
                releaseTransportMediators(mediators);
            }
        }
        return 0;
    }

    public long getStartTime() {
        return this.startTime;
    }

    public void setStartTime(final long startTime) {
        this.startTime = startTime;
    }

    public long getArrivalTime() {
        return this.arrivalTime;
    }

    public void setArrivalTime(final long arrivalTime) {
        this.arrivalTime = arrivalTime;
    }

    public boolean getTraceState() {
        return isTraceRequested();
    }

    public void setTraceState(final boolean traceState) {
        setTraceRequested(traceState);
    }

    public void setParentCorrelator(final ArmCorrelator parentCorrelator) {
        this.parentCorrelator = parentCorrelator;
    }

    public long getBlockingHandle() {
        return ++this.blockedHandle;
    }

    public boolean equals(final Object o) {
        if (this == o) return true;
        if (!(o instanceof ArmTransactionImpl)) return false;
        if (!super.equals(o)) return false;

        final ArmTransactionImpl armTransaction = (ArmTransactionImpl) o;

        return application.equals(armTransaction.application) && null != contextProperties
               ? !contextProperties.equals(armTransaction.contextProperties)
               : null == armTransaction.contextProperties && null != localCorrelator
                 ? !localCorrelator.equals(armTransaction.localCorrelator)
                 : null == armTransaction.localCorrelator && null != parentCorrelator
                   ? !parentCorrelator.equals(armTransaction.parentCorrelator)
                   : !(null == armTransaction.parentCorrelator && !transactionDefiniton.equals(
                           armTransaction.transactionDefiniton)) && !(null != user
                                                                      ? !user.equals(armTransaction.user)
                                                                      : null != armTransaction.user);

    }

    public int hashCode() {
        int result = super.hashCode();
        result = HASH_CODE_MAGIC_NUMBER * result + transactionDefiniton.hashCode();
        result = HASH_CODE_MAGIC_NUMBER * result + application.hashCode();
        result = HASH_CODE_MAGIC_NUMBER * result + (null != user ? user.hashCode() : 0);
        result = HASH_CODE_MAGIC_NUMBER * result + (null != parentCorrelator ? parentCorrelator.hashCode() : 0);
        result = HASH_CODE_MAGIC_NUMBER * result + (null != contextProperties ? contextProperties.hashCode() : 0);
        result = HASH_CODE_MAGIC_NUMBER * result + (null != localCorrelator ? localCorrelator.hashCode() : 0);
        return result;
    }
}
